
# ******************************************************
# * Copyright © 2017-2023 - Jordan Irwin (AntumDeluge) *
# ******************************************************
# * This software is licensed under the MIT license.   *
# * See: LICENSE.txt for details.                      *
# ******************************************************

## @module ui.checklist

import wx

from dbr.language      import GT
from libdbr            import strings
from libdbr.logger     import Logger
from libdebreate.ident import btnid
from libdebreate.ident import pnlid
from ui.button         import CreateButton
from ui.helper         import GetField
from ui.layout         import BoxSizer
from ui.panel          import BorderedPanel
from ui.panel          import ScrolledPanel
from ui.style          import layout as lyt


_logger = Logger(__name__)

## A checkable list
class CheckList(BorderedPanel):
  def __init__(self, parent, ID=wx.ID_ANY, items=[], pos=wx.DefaultPosition, size=wx.DefaultSize,
      style=wx.VSCROLL, name=wx.PanelNameStr):

    BorderedPanel.__init__(self, parent, ID, pos, size, name=name)

    self.SetBackgroundColour("white")

    pnl_bg = ScrolledPanel(self, pnlid.BACKGROUND, style=style|wx.TAB_TRAVERSAL|wx.BORDER_NONE)
    pnl_bg.SetBackgroundColour(self.GetBackgroundColour())
    pnl_bg.SetScrollbars(20, 20, 50, 50)

    # *** Event Handling *** #

    self.Bind(wx.EVT_CHECKBOX, self.OnCheckItem)

    # *** Layout *** #

    lyt_bg = BoxSizer(wx.VERTICAL)

    pnl_bg.SetSizer(lyt_bg)
    pnl_bg.SetAutoLayout(True)

    lyt_main = BoxSizer(wx.VERTICAL)
    lyt_main.Add(pnl_bg, 1, wx.EXPAND)

    self.SetSizer(lyt_main)
    self.SetAutoLayout(True)
    self.Layout()

    # *** Post-Layout Actions *** #

    if items:
      self.AddItems(items)

  ## Add a single item to the list
  #
  #  @param label
  #    Item's displayed text
  #  @param checked
  #    Sets item checked if True
  def AddItem(self, label, checked=False):
    # Yield for progress dialog pulse updates
    wx.GetApp().Yield()

    # FIXME: Static lines are counted
    item_index = self.GetItemCount()

    #_logger.debug(GT("Lintian tag: {}; Set checked: {}").format(label, checked))

    pnl_bg = GetField(self, pnlid.BACKGROUND)
    lyt_bg = pnl_bg.GetSizer()

    lyt_bg.Add(wx.CheckBox(pnl_bg, label=label), 1, wx.EXPAND)
    lyt_bg.Add(wx.StaticLine(pnl_bg, style=wx.LI_HORIZONTAL), 0, wx.EXPAND)

    if checked:
      check_box = self.GetItem(item_index)

      if isinstance(check_box, wx.CheckBox):
        check_box.SetValue(True)

    pnl_bg.Layout()
    self.Layout()

  ## Adds multiple items to the list
  #
  #  @param labels
  #    List of text labels to be displayed
  #  @param checked
  #    Sets items as checked if True
  def AddItems(self, labels, checked=False):
    for l in labels:
      _logger.debug("Adding item: {} (checked={})".format(l, checked))
      self.AddItem(l, checked)

  ## Sets all item states to 'unchecked'
  def Clear(self):
    changed = False
    for CHK in self.GetAllItems():
      if CHK.GetValue():
        CHK.SetValue(False)
        changed = True
    if changed:
      self.PostCheckBoxEvent()

  ## Retrieves all check boxes
  #
  #  @return
  #    Tuple containing all children check box instances
  def GetAllItems(self):
    pnl_bg = GetField(self, pnlid.BACKGROUND)
    items = []
    for item in pnl_bg.GetChildren():
      if isinstance(item, wx.CheckBox):
        items.append(item)
    return tuple(items)

  ## Retrieves all item labels
  #
  #  @return
  #    Tuple of all check box labels
  def GetAllLabels(self):
    labels = []
    for CHK in self.GetAllItems():
      labels.append(CHK.GetLabel())
    return tuple(labels)

  ## Retrieves number of items in list that set as 'checked'
  def GetCheckedCount(self):
    checked_count = 0
    for CHK in self.GetAllItems():
      if CHK.GetValue():
        checked_count += 1
    return checked_count

  ## Retrieves list of items that are set as 'checked'
  def GetCheckedLabels(self):
    checked_list = []
    for CHK in self.GetAllItems():
      if CHK.IsChecked():
        label = CHK.GetLabel()
        _logger.debug(GT("Retrieving checked label: {}").format(label))
        checked_list.append(label)
    return tuple(checked_list)

  ## Retrieves a single item
  #
  #  @param index
  #    Retrieve item at index
  #  @return
  #    True if label is found
  def GetItem(self, index):
    return self.GetAllItems()[index]

  ## Retrieves an item using a label
  #
  #  @param label
  #    Text label to search for
  def GetItemByLabel(self, label):
    labels = self.GetAllLabels()
    if label in labels:
      return self.GetItem(labels.index(label))

  ## Retrieves number of items in list
  def GetItemCount(self):
    return len(self.GetAllItems())

  ## Retrieves the checked state of an item
  #
  #  @param index
  #    \b \e Integer index of of item to check
  def GetValue(self, index):
    return self.GetItem(index).GetValue()

  ## Retrieves the checked state of an item
  #
  #  @param label
  #    Label whose value to get
  #  @return
  #    \b \e True if label's state is 'checked'
  def GetValueByLabel(self, label):
    item = self.GetItemByLabel(label)
    if item:
      return item.GetValue()
    return False

  ## Checks if any item's state is 'checked'
  #
  #  @return
  #    \b \e True if any item's state is 'checked'
  def HasSelected(self):
    for CHK in self.GetAllItems():
      if CHK.GetValue():
        return True
    return False

  ## Check if an item's state is 'checked'
  #
  #  @param label
  #    Label of item to check
  def IsSelected(self, label):
    return label in self.GetCheckedLabels()

  ## Checks if a label is in the list
  #
  #  @param label
  #    Text label to search for
  def LabelExists(self, label):
    for LABEL in self.GetAllLabels():
      if LABEL == label:
        return True
    return False

  ## Handles check box events
  def OnCheckItem(self, event=None):
    self.PostCheckBoxEvent()

  ## Propagates check box event to parent or target
  #
  #  @param target
  #    \b \e wx.Window instance to post event to
  def PostCheckBoxEvent(self, target=None):
    if not target:
      target = self.Parent

    wx.PostEvent(target, wx.CommandEvent(wx.wxEVT_COMMAND_CHECKBOX_CLICKED, self.Id))

  ## @todo Doxygen
  def ScrollToEnd(self):
    pnl_bg = GetField(self, pnlid.BACKGROUND)

    pnl_bg.SetScrollPos(wx.VERTICAL, pnl_bg.GetScrollLines(wx.VERTICAL))

    pnl_bg.Refresh()
    self.Refresh()

  ## @todo Define & Doxygen
  def SetItemCheckedByLabel(self, label, checked=True):
    # FIXME: Should use a more efficient method to index & retrieve items
    for C in self.GetAllItems():
      if C.GetLabel() == label:
        C.SetValue(checked)
        break

  ## Sets the 'checked' state of an item
  #
  #  @param index
  #    \b \e Integer index of item to manipulate
  #  @param checked
  #    State to set item
  def SetValue(self, index, checked):
    return self.GetItem(index).SetValue(checked)


## @todo Doxygen
class CheckListDialog(wx.Dialog):
  def __init__(self, parent, ID=wx.ID_ANY, title=wx.EmptyString, pos=wx.DefaultPosition,
      size=wx.DefaultSize, style=wx.DEFAULT_DIALOG_STYLE, name=wx.DialogNameStr,
      allow_custom=False):
    wx.Dialog.__init__(self, parent, ID, title, pos, size, style=style|wx.RESIZE_BORDER,
        name=name)

    self.check_list = CheckList(self)

    # *** Buttons *** #

    layout_buttons = BoxSizer(wx.HORIZONTAL)

    btn_clear = CreateButton(self, btnid.CLEAR)
    btn_confirm = CreateButton(self, btnid.CONFIRM)
    btn_cancel = CreateButton(self, btnid.CANCEL)

    btn_clear.Bind(wx.EVT_BUTTON, self.OnClearList)

    # FIXME: Correct button order?
    layout_buttons.Add(btn_clear, 0, wx.LEFT, 5)
    layout_buttons.AddStretchSpacer(1)
    layout_buttons.Add(btn_confirm, 0, wx.RIGHT, 5)
    layout_buttons.Add(btn_cancel, 0, wx.RIGHT, 5)

    # *** Layout *** #

    layout_main = BoxSizer(wx.VERTICAL)

    layout_main.Add(self.check_list, 1, wx.EXPAND|wx.ALL, 5)
    layout_main.Add(layout_buttons, 0, wx.EXPAND|wx.BOTTOM, 5)

    if allow_custom:
      btn_add_custom = wx.Button(self, label=GT("Add custom"))
      btn_add_custom.Bind(wx.EVT_BUTTON, self.OnAddCustom)

      self.input_add_custom = wx.TextCtrl(self)

      layout_add_custom = BoxSizer(wx.HORIZONTAL)
      layout_add_custom.Add(btn_add_custom, 0, lyt.PAD_LR, 5)
      layout_add_custom.Add(self.input_add_custom, 1, wx.RIGHT, 5)

      layout_main.Insert(1, layout_add_custom, 0, wx.EXPAND)

    self.SetSizer(layout_main)
    self.SetAutoLayout(True)
    self.Layout()

    self.CenterOnParent()

  ## @todo Doxygen
  def AddItem(self, label, checked=False):
    self.check_list.AddItem(label, checked)

  ## @todo Doxygen
  def GetCheckedCount(self):
    return self.check_list.GetCheckedCount()

  ## @todo Doxygen
  def GetCheckedLabels(self):
    return self.check_list.GetCheckedLabels()

  ## @todo Doxygen
  def InitCheckList(self, labels, checked=False):
    self.check_list.AddItems(labels, checked)

  ## @todo Doxygen
  def OnAddCustom(self, event=None):
    custom_label = strings.toString(self.input_add_custom.GetValue()).strip(" ").replace(" ", "_")
    if not strings.isEmpty(custom_label) and not self.check_list.LabelExists(custom_label):
      self.check_list.AddItem(custom_label, True)
      self.check_list.ScrollToEnd()

  ## @todo Doxygen
  def OnClearList(self, event=None):
    if self.GetCheckedCount():
      warn_dialog = wx.MessageDialog(self, GT("Clear Lintian overrides list?"), GT("Warning"),
          style=wx.YES_NO|wx.NO_DEFAULT|wx.ICON_WARNING)
      warn_dialog.CenterOnParent()

      if warn_dialog.ShowModal() == wx.ID_YES:
        self.check_list.Clear()

  ## @todo Doxygen
  def SetItemCheckedByLabel(self, label, checked=True):
    self.check_list.SetItemCheckedByLabel(label, checked)

  ## @todo Doxygen
  def ShowModal(self):
    # wx 2.8 doesn't automatically center on parent
    if self.Parent:
      self.CenterOnParent()
    return wx.Dialog.ShowModal(self)
